package com.pig.easy.bpm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.pig.easy.bpm.dto.request.AddUserDTO;
import com.pig.easy.bpm.dto.request.UserQueryDTO;
import com.pig.easy.bpm.dto.request.UserUpdateDTO;
import com.pig.easy.bpm.dto.response.ListDTO;
import com.pig.easy.bpm.dto.response.TreeDTO;
import com.pig.easy.bpm.dto.response.UserInfoDTO;
import com.pig.easy.bpm.dto.response.UserRoleDetailDTO;
import com.pig.easy.bpm.entity.UserDO;
import com.pig.easy.bpm.entityError.EntityError;
import com.pig.easy.bpm.mapper.UserMapper;
import com.pig.easy.bpm.service.CompanyService;
import com.pig.easy.bpm.service.DeptService;
import com.pig.easy.bpm.service.UserRoleService;
import com.pig.easy.bpm.service.UserService;
import com.pig.easy.bpm.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author pig
 * @since 2020-05-14
 */
@DubboService
@Slf4j
public class UserServiceImpl extends BeseServiceImpl<UserMapper, UserDO> implements UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    DeptService deptService;

    @Autowired
    CompanyService companyService;

    @Autowired
    UserRoleService userRoleService;

    /* 默认用户名密码，可以放Nacos中 */
    private String DEFALT_USER_PASSWORD = "123456";

    /* 也可以放入 nacos 配置 */
    private static final String SECRET_KEY = "best-bpm-pig-sds13232fdfweHGHGHFGFArwersdfdafsdfsdfsdfsd"; //秘钥
    private static final long TOKEN_EXPIRE_TIME = 12 * 60 * 60 * 1000; //token过期时间
    private static final long REFRESH_TOKEN_EXPIRE_TIME = 24 * 60 * 60 * 1000; //refreshToken过期时间
    private static final String ISSUER = "best-bpm-pig"; //签发人
    private static final String USER_ACCESS_TOKEN = "best:bpm:user:accessToken:";
    private static final String USER = "best:bpm:user";

    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public Result<String> generateToken(Long userId) {
        return Result.responseSuccess(JWTUtil.generateToken(SECRET_KEY, TOKEN_EXPIRE_TIME, ISSUER, userId));
    }

    @Override
    public Result<UserInfoDTO> verifyToken(Long userId, String token) {

        BestBpmAsset.isAssetEmpty(userId);
        BestBpmAsset.isAssetEmpty(token);

        Result<Long> result = getUserIdByToken(token);
        if (result.getEntityError().getCode() != EntityError.SUCCESS.getCode()) {
            return Result.responseError(result.getEntityError());
        }


        if (!userId.equals(result.getData()) || !JWTUtil.verify(SECRET_KEY, ISSUER, token)) {
            return Result.responseError(EntityError.ILLEGAL_ACCESS_ERROR);
        }


        Result<UserInfoDTO> result1 = getUserInfo(Long.toString(userId));
        if (result1.getEntityError().getCode() != EntityError.SUCCESS.getCode()) {
            return Result.responseError(result1.getEntityError());
        }
        return Result.responseSuccess(result1.getData());
    }

    @Override
    public Result<Long> getUserIdByToken(String token) {
        BestBpmAsset.isAssetEmpty(token);
        return Result.responseSuccess(JWTUtil.getUserId(token));
    }

    @Override
    public Result<UserInfoDTO> login(String userName, String password) {

        BestBpmAsset.isAssetEmpty(userName);
        BestBpmAsset.isAssetEmpty(password);

        UserDO user = userMapper.getUser(userName);
        if (user == null) {
            return Result.responseError(EntityError.USERNAME_OR_PASSWORD_ERROR);
        }
        if (!new BCryptPasswordEncoder().matches(password, user.getPassword())) {
            return Result.responseError(EntityError.USERNAME_OR_PASSWORD_ERROR);
        }
        UserInfoDTO userInfoDTO = BeanUtils.switchToDTO(user, UserInfoDTO.class);
        userInfoDTO.setAccessToken(generateToken(user.getUserId()).getData());
        setUserInfoValues(userInfoDTO);
        redisTemplate.opsForValue().set(USER_ACCESS_TOKEN + userInfoDTO.getAccessToken(), userInfoDTO.getAccessToken(), TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
        redisTemplate.opsForHash().put(USER, CommonUtils.evalString(userInfoDTO.getUserId()), userInfoDTO);
        return Result.responseSuccess(userInfoDTO);
    }

    @Override
    public Result<UserInfoDTO> getUserInfo(String userName) {

        BestBpmAsset.isAssetEmpty(userName);
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        if (redisTemplate.opsForHash().get(USER, userName) == null) {
            UserDO user = userMapper.getUser(userName);
            if (user == null) {
                return Result.responseError(EntityError.USERNAME_OR_PASSWORD_ERROR);
            }
            userInfoDTO = BeanUtils.switchToDTO(user, UserInfoDTO.class);
            setUserInfoValues(userInfoDTO);
            redisTemplate.opsForHash().put(USER, CommonUtils.evalString(userInfoDTO.getUserId()), userInfoDTO);
        } else {
            userInfoDTO = (UserInfoDTO) redisTemplate.opsForHash().get(USER, userName);
        }

        return Result.responseSuccess(userInfoDTO);
    }

    @Override
    public Result<UserInfoDTO> getUserInfoById(Long userId) {
        BestBpmAsset.isAssetEmpty(userId);
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        if (redisTemplate.opsForHash().get(USER, userId.toString()) == null) {

            UserDO user = userMapper.selectOne(new QueryWrapper<>(UserDO.builder().userId(userId).validState(VALID_STATE).build()));
            if (user == null) {
                return Result.responseError(EntityError.USERNAME_OR_PASSWORD_ERROR);
            }
            userInfoDTO = BeanUtils.switchToDTO(user, UserInfoDTO.class);
            setUserInfoValues(userInfoDTO);
            redisTemplate.opsForHash().put(USER, userInfoDTO.getUserId().toString(), userInfoDTO);
        } else {
            userInfoDTO = (UserInfoDTO) redisTemplate.opsForHash().get(USER, userId.toString());
        }
        return Result.responseSuccess(userInfoDTO);
    }

    @Override
    public Result<UserInfoDTO> addUser(AddUserDTO addUserDTO) {

        BestBpmAsset.isEmpty(addUserDTO.getUserName(), "用户名不能为空！");
        BestBpmAsset.isEmpty(addUserDTO.getEmail(), "邮箱不能为空！");

        /* 使用BCryptPasswordEncoder 加密 */
        addUserDTO.setPassword(new BCryptPasswordEncoder().encode(StringUtils.isEmpty(addUserDTO.getPassword()) ? DEFALT_USER_PASSWORD : addUserDTO.getPassword()));
        UserDO user = BeanUtils.switchToDO(addUserDTO, UserDO.class);
        user.setUserId(userMapper.getUserId());
        userMapper.insert(user);
        UserInfoDTO userInfoDTO = BeanUtils.switchToDTO(user, UserInfoDTO.class);
        setUserInfoValues(userInfoDTO);
        return Result.responseSuccess(userInfoDTO);
    }

    @Override
    public Result<Boolean> logout(Long userId, String accessToken) {

        Result<UserInfoDTO> result = verifyToken(userId, accessToken);
        if (result.getEntityError().getCode() != EntityError.SUCCESS.getCode()) {
            return Result.responseError(result.getEntityError());
        }
        redisTemplate.delete(USER_ACCESS_TOKEN + accessToken);
        redisTemplate.opsForHash().delete(USER, userId.toString());

        return Result.responseSuccess(true);
    }

    @Override
    public Result<PageInfo<UserInfoDTO>> getListByCondition(UserQueryDTO userQueryDTO) {

        BestBpmAsset.isAssetEmpty(userQueryDTO);
        int pageIndex = CommonUtils.evalInt(userQueryDTO.getPageIndex(), DEFAULT_PAGE_INDEX);
        int pageSize = CommonUtils.evalInt(userQueryDTO.getPageSize(), DEFAULT_PAGE_SIZE);

        PageHelper.startPage(pageIndex, pageSize);
        List<UserInfoDTO> result = userMapper.getListByCondition(userQueryDTO);
        if (result == null) {
            result = new ArrayList<>();
        }
        PageInfo<UserInfoDTO> pageInfo = new PageInfo<>(result);
        return Result.responseSuccess(pageInfo);
    }

    @Override
    public Result<List<UserInfoDTO>> getList(UserQueryDTO userQueryDTO) {

        BestBpmAsset.isAssetEmpty(userQueryDTO);
        int pageIndex = CommonUtils.evalInt(userQueryDTO.getPageIndex(), DEFAULT_PAGE_INDEX);
        int pageSize = CommonUtils.evalInt(userQueryDTO.getPageSize(), MAX_PAGE_SIZE);
        userQueryDTO.setPageIndex(pageIndex);
        userQueryDTO.setPageSize(pageSize);
        Result<PageInfo<UserInfoDTO>> result = getListByCondition(userQueryDTO);

        return Result.responseSuccess(result.getData().getList());
    }

    @Override
    public Result<List<TreeDTO>> getOrganUserTree(String tenantId, Long parentCompantId) {

        BestBpmAsset.isEmpty(tenantId);
        BestBpmAsset.isEmpty(parentCompantId);

        /* 人只能挂在部门下 */
        Result<List<TreeDTO>> result = deptService.getOrganTree(tenantId, parentCompantId);
        if (result.getEntityError().getCode() != EntityError.SUCCESS.getCode()) {
            return Result.responseError(result.getEntityError());
        }
        List<TreeDTO> organUserTreeList = new CopyOnWriteArrayList<>();

        for (TreeDTO treeDTO : result.getData()) {
            organUserTreeList.add(makeOrganTree(treeDTO,tenantId,treeDTO.getTreeId(),3));
        }

        return Result.responseSuccess(organUserTreeList);
    }

    @Override
    public Result<Integer> updateUser(UserUpdateDTO userUpdateDTO) {

        BestBpmAsset.isAssetEmpty(userUpdateDTO);
        BestBpmAsset.isAssetEmpty(userUpdateDTO.getId());

        UserDO userDO = BeanUtils.switchToDO(userUpdateDTO,UserDO.class);
        int num = userMapper.updateById(userDO);
        return Result.responseSuccess(num);
    }

    private TreeDTO makeOrganTree(TreeDTO tree, String tenantId, Long parentId, Integer treeType) {

        List<TreeDTO> tempList = new ArrayList<>();
        if (tree.getChildren() == null) {
            tree.setChildren(new ArrayList<>());
        }

        if(tree.getTreeType().equals(2)){
            UserQueryDTO userQueryDTO = new UserQueryDTO();
            userQueryDTO.setPageIndex(DEFAULT_PAGE_INDEX);
            userQueryDTO.setPageSize(MAX_PAGE_SIZE);
            userQueryDTO.setTenantId(tenantId);
            userQueryDTO.setDeptId(parentId);
            Result<List<UserInfoDTO>> result = getList(userQueryDTO);
            if (result.getEntityError().getCode() != EntityError.SUCCESS.getCode()) {
                log.error("makeOrganTree error,message :{}", result.getEntityError());
                return tree;
            }

            TreeDTO treeDTO = new TreeDTO();
            for(UserInfoDTO userInfoDTO: result.getData()){
                treeDTO = new TreeDTO();
                treeDTO.setId(SnowKeyGenUtils.getInstance().getNextId());
                treeDTO.setTreeId(userInfoDTO.getUserId());
                treeDTO.setTreeCode(userInfoDTO.getUserName());
                treeDTO.setTreeName(userInfoDTO.getRealName());
                treeDTO.setTempTreeId(userInfoDTO.getCompanyId());
                treeDTO.setParentId(userInfoDTO.getDeptId());
                treeDTO.setTreeTypeCode("user");
                treeDTO.setTreeType(3);
                tempList.add(treeDTO);
            }
        }
        List<TreeDTO> children = tree.getChildren();

        for (TreeDTO temp : children) {
           // if (temp.getTreeType().equals(2)) {
                tempList.add(makeOrganTree(temp, tenantId, temp.getTreeId(), treeType));
          //  }
        }
        tree.setChildren(tempList);
        return tree;
    }

    private void setUserInfoValues(UserInfoDTO userInfoDTO) {

        if (userInfoDTO == null
                || CommonUtils.evalLong(userInfoDTO.getUserId()) < 0) {
            return;
        }

        Result<List<UserRoleDetailDTO>> result = userRoleService.getUserRoleDetailByUserId(userInfoDTO.getUserId());
        if (result.getEntityError().getCode() != EntityError.SUCCESS.getCode()) {
            return;
        }
        List<UserRoleDetailDTO> userRoleList = result.getData();
        ListDTO listDTO = new ListDTO();
        // set Company List
        List<ListDTO> companyList = new ArrayList<>();

        Map<Long, UserRoleDetailDTO> companyMap = userRoleList.stream().collect(Collectors.toMap(UserRoleDetailDTO::getCompanyId, a -> a, (oldVal, newVal) -> newVal));
        for (Map.Entry<Long, UserRoleDetailDTO> companyEntry : companyMap.entrySet()) {
            listDTO = new ListDTO();
            listDTO.setId(companyEntry.getKey());
            listDTO.setCode(companyEntry.getValue().getCompanyCode());
            listDTO.setName(companyEntry.getValue().getCompanyName());
            companyList.add(listDTO);
        }
        userInfoDTO.setCompanyList(companyList);
        if (companyMap.get(userInfoDTO.getCompanyId()) != null) {
            userInfoDTO.setCompanyCode(companyMap.get(userInfoDTO.getCompanyId()).getCompanyCode());
            userInfoDTO.setCompanyName(companyMap.get(userInfoDTO.getCompanyId()).getCompanyName());
        }

        // set Dept List
        List<ListDTO> deptList = new ArrayList<>();
        Map<Long, UserRoleDetailDTO> deptMap = userRoleList.stream().collect(Collectors.toMap(UserRoleDetailDTO::getDeptId, a -> a, (oldVal, newVal) -> newVal));
        for (Map.Entry<Long, UserRoleDetailDTO> deptEntry : deptMap.entrySet()) {
            listDTO = new ListDTO();
            listDTO.setId(deptEntry.getKey());
            listDTO.setCode(deptEntry.getValue().getDeptCode());
            listDTO.setName(deptEntry.getValue().getDeptName());
            deptList.add(listDTO);
        }

        userInfoDTO.setDeptList(deptList);
        if (companyMap.get(userInfoDTO.getDeptId()) != null) {
            userInfoDTO.setDeptCode(companyMap.get(userInfoDTO.getDeptId()).getDeptCode());
            userInfoDTO.setDeptName(companyMap.get(userInfoDTO.getCompanyId()).getDeptName());
        }
        // set Role List
        List<ListDTO> roleList = new ArrayList<>();
        Map<Long, UserRoleDetailDTO> roleMap = userRoleList.stream().collect(Collectors.toMap(UserRoleDetailDTO::getRoleId, a -> a, (oldVal, newVal) -> newVal));
        for (Map.Entry<Long, UserRoleDetailDTO> roleEntry : roleMap.entrySet()) {
            listDTO = new ListDTO();
            listDTO.setId(roleEntry.getKey());
            listDTO.setCode(roleEntry.getValue().getRoleCode());
            listDTO.setName(roleEntry.getValue().getRoleName());
            roleList.add(listDTO);
        }
        userInfoDTO.setRoleList(roleList);
    }

}
